<?php


namespace App\Container;


use App\Container\Exceptions\NotExistException;
use Noodlehaus\Config;

class Container
{
    protected $items = [];

    public function __construct()
    {
        $this->loadServices();
    }

    // 预加载
    protected function loadServices()
    {
        $this->share(Config::class,function (){
            return new Config(__DIR__.'/../../config');
        });
    }

    public function get($name)
    {
        if(!$this->has($name))
        {
            return $this->autowire($name);
        }
        return $this->items[$name]();
    }

    protected function autowire($name)
    {
        if(!class_exists($name))
        {
            throw new NotExistException();
        }
        // 反射类
        $reflector  = new \ReflectionClass($name);
        // 判断类是否可以实例化
        if(!$reflector->isInstantiable())
        {
            throw new NotExistException();
        }
        // 获取构造函数，判断是否需要注入构造函数中的类
        if($constructor = $reflector->getConstructor())
        {
            // 获取依赖对象
            $dependencies = $this->getDependencies($constructor);
            // 创建一个类的新实例，给出的参数将传递到类的构造函数。
            return $reflector->newInstanceArgs($dependencies);
        }
        return new $name();
    }

    protected function getDependencies(\ReflectionMethod $method)
    {
        // 获取参数名称 可能是多个
        $params = $method->getParameters();
        // 循环注入类名
        return array_map(function(\ReflectionParameter $parameter){
            // 获取类名 包含 命名空间
            $class = $parameter->getClass();
            if (!$class)
            {
                throw new NotExistException();
            }
            return $this->get($class->getName());

        },$params);
    }

    public function add($name, callable $closure)
    {
        $this->items[$name] = $closure;
    }

    public function share($name, callable $closure)
    {
        $this->items[$name] = function ()use($closure){
            // 单例模式
            static $service;
            if(!$service)
            {
                $service = $closure($this);
            }
            return $service;
;       };

    }

    public function has($name)
    {
        return isset($this->items[$name]);
    }
}
